package com.mes.bac.backend.service.impl;

import com.mes.bac.backend.entity.*;
import com.mes.bac.backend.mapper.*;
import com.mes.bac.backend.service.IForecastService;
import com.mes.bac.backend.service.IProductService;
import com.mes.bac.backend.vo.ForecastResultVO;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

@Slf4j
@Service
public class ForecastServiceImpl implements IForecastService {

    // 自动注入所需的所有数据访问层和服务层接口
    @Autowired private IProductService productService;
    @Autowired private ProdWkproMapper prodWkproMapper;
    @Autowired private WorkstationMapper workstationMapper;
    @Autowired private ProcessTimeMapper processTimeMapper;

    /**
     * 内部数据类：用于在模拟中代表一个工件。
     * 它非常轻量，只包含核心信息，以提升模拟性能。
     */
    @Data
    private static class SimPiece {
        private String pid; // 所属产品ID
        private int currentRouteIndex = -1; // 当前在工艺路线中的位置索引, -1表示还未开始
    }

    /**
     * 内部数据类：用于在内存中追踪每个工位的实时状态。
     */
    private static class WorkstationState {
        boolean busy = false; // 工位是否正在加工
        SimPiece currentPiece = null; // 当前正在加工的工件
        long finishTime = -1; // 预计完成时间点 (单位：分钟)

        // 将工位设置为空闲
        void setIdle() {
            this.busy = false;
            this.currentPiece = null;
            this.finishTime = -1;
        }

        // 将工位设置为繁忙
        void setBusy(SimPiece piece, long finishTime) {
            this.busy = true;
            this.currentPiece = piece;
            this.finishTime = finishTime;
        }
    }

    // 实例级缓存，用于在一次预测中复用数据，避免反复查询数据库，极大提升性能
    private final Map<String, List<ProdWkpro>> routingCache = new HashMap<>(); // key: 产品ID, value: 该产品的有序工艺路线列表
    private final Map<Integer, List<Workstation>> stationCache = new HashMap<>(); // key: 工序ID, value: 能执行该工序的所有工位列表

    /**
     * 执行产能潜力预测的核心方法。
     * @param targetDate 预测的目标日期
     * @return 包含时间轴和各产品产量数据的预测结果
     */
    @Override
    public ForecastResultVO predict(LocalDate targetDate) {
        // --- 1. 初始化模拟环境 ---
        initializeCaches(); // 加载并缓存所有基础规则数据

        // 计算总的模拟时长
        LocalDate today = LocalDate.now();
        long totalDays = ChronoUnit.DAYS.between(today, targetDate) + 1;
        if (totalDays <= 0) return new ForecastResultVO(); // 如果目标日期是过去，则直接返回空结果
        long totalSimulationMinutes = totalDays * 8 * 60; // 每天8小时工作制

        // 准备基础信息
        List<Product> allProducts = productService.list();
        Map<String, Integer> processTimeMap = buildProcessTimeMap(); // 构建 "产品ID:工位ID" -> 耗时 的快速查找表

        // 初始化虚拟工厂：为每个工位创建状态对象和等待队列
        Map<String, WorkstationState> workstationStates = new HashMap<>();
        Map<String, Queue<SimPiece>> workstationQueues = new HashMap<>();
        for (Workstation ws : workstationMapper.selectList(null)) {
            workstationStates.put(ws.getId(), new WorkstationState());
            workstationQueues.put(ws.getId(), new LinkedList<>());
        }

        // 初始化用于返回给前端的数据结构
        ForecastResultVO result = new ForecastResultVO();
        result.setProductInfo(allProducts.stream().collect(Collectors.toMap(Product::getId, Product::getName)));
        result.setTimeline(IntStream.range(0, (int) totalDays).mapToObj(i -> today.plusDays(i).format(DateTimeFormatter.ISO_LOCAL_DATE)).collect(Collectors.toList()));
        Map<String, List<Integer>> seriesData = allProducts.stream().collect(Collectors.toMap(Product::getId, id -> new ArrayList<>()));
        result.setSeries(seriesData);
        Map<String, Integer> completedProductCount = allProducts.stream().collect(Collectors.toMap(Product::getId, p -> 0));

        // --- 2. 开始时间推进的模拟循环 ---
        for (long minute = 0; minute < totalSimulationMinutes; minute++) {

            // 2a. 模拟持续投产：假设原材料无限，周期性地将所有产品投入生产线
            if (minute % 60 == 0) { // 每60分钟（1小时）投产一轮
                for (Product product : allProducts) {
                    if (routingCache.containsKey(product.getId())) { // 确保该产品有工艺路线
                        SimPiece newPiece = new SimPiece();
                        newPiece.setPid(product.getId());
                        // 将新工件调度到它的第一道工序
                        scheduleNextStep(newPiece, workstationQueues, workstationStates);
                    }
                }
            }

            // 2b. 遍历所有工位，处理当前分钟可能发生的事件
            for (String wsid : workstationStates.keySet()) {
                WorkstationState state = workstationStates.get(wsid);

                // 事件一：检查是否有任务完成
                if (state.busy && state.finishTime <= minute) {
                    SimPiece finishedPiece = state.currentPiece;

                    // 检查是否是最后一道工序
                    if (isLastProcess(finishedPiece, wsid)) {
                        // 是，则该产品最终完成数量+1
                        completedProductCount.merge(finishedPiece.getPid(), 1, Integer::sum);
                    } else {
                        // 否，则将其调度到下一站
                        scheduleNextStep(finishedPiece, workstationQueues, workstationStates);
                    }
                    // 任务处理完毕，将工位设置为空闲
                    state.setIdle();
                }

                // 事件二：检查工位是否可以启动新任务
                if (!state.busy && !workstationQueues.get(wsid).isEmpty()) {
                    SimPiece newTask = workstationQueues.get(wsid).poll(); // 从等待队列中取出一个任务
                    String timeKey = newTask.getPid() + ":" + wsid;
                    Integer elapsed = processTimeMap.getOrDefault(timeKey, 60); // 查找标准耗时，找不到则默认60分钟
                    state.setBusy(newTask, minute + elapsed); // 将工位设为繁忙，并计算好预计完成时间
                }
            }

            // 2c. 记录每日数据快照
            if ((minute + 1) % (8 * 60) == 0) { // 判断是否一天的结束
                for (String pid : seriesData.keySet()) {
                    seriesData.get(pid).add(completedProductCount.get(pid));
                }
            }
        }

        // 循环结束后，可能最后一天的数据还没记录，这里做一次补充
        if (allProducts.size() > 0 && seriesData.get(allProducts.get(0).getId()).size() < totalDays) {
            for (String pid : seriesData.keySet()) {
                seriesData.get(pid).add(completedProductCount.get(pid));
            }
        }
        return result;
    }

    /**
     * 辅助方法：将虚拟工件调度到其下一个工序。
     * @param piece 要被调度的工件
     * @param queues 所有工位的等待队列
     * @param workstationStates 所有工位的状态
     */
    private void scheduleNextStep(SimPiece piece, Map<String, Queue<SimPiece>> queues, Map<String, WorkstationState> workstationStates) {
        List<ProdWkpro> route = routingCache.get(piece.getPid());
        if (route == null || route.isEmpty()) return; // 如果没有工艺路线，无法调度

        // 计算下一个工序的索引
        int nextIndex = piece.getCurrentRouteIndex() + 1;
        if (nextIndex >= route.size()) return; // 已经是最后一道工序，无法调度

        // 更新工件自身的状态
        piece.setCurrentRouteIndex(nextIndex);
        ProdWkpro nextProcess = route.get(nextIndex);

        // 找到所有能执行该工序的工位
        List<Workstation> availableStations = stationCache.getOrDefault(nextProcess.getWpid(), Collections.emptyList());
        if(availableStations.isEmpty()){
            log.warn("产品 {} 的工序 (wpid={}) 找不到任何可用工位，该工件将被丢弃。", piece.getPid(), nextProcess.getWpid());
            return;
        }

        // 调度算法：选择“当前队列长度 + 是否繁忙”总和最小的那个工位
        String bestStationId = availableStations.stream()
                .min(Comparator.comparingInt(ws -> queues.get(ws.getId()).size() + (workstationStates.get(ws.getId()).busy ? 1 : 0)))
                .map(Workstation::getId)
                .orElse(null);

        // 如果找到了最优工位，则将工件加入其等待队列
        if (bestStationId != null) {
            queues.get(bestStationId).add(piece);
        }
    }

    // --- 其他初始化和辅助方法 ---

    /**
     * 检查一个刚完成的任务是否是其产品的最后一道工序。
     */
    private boolean isLastProcess(SimPiece piece, String currentWsid) {
        List<ProdWkpro> route = routingCache.get(piece.getPid());
        if (route == null || route.isEmpty()) return false;
        // 获取工艺路线的最后一个工序ID
        Integer lastWpId = route.get(route.size() - 1).getWpid();
        // 获取能执行该工序的所有工位
        List<Workstation> lastStations = stationCache.getOrDefault(lastWpId, Collections.emptyList());
        // 判断当前工位是否在这些“终点工位”之中
        return lastStations.stream().anyMatch(ws -> ws.getId().equals(currentWsid));
    }

    /**
     * 预加载所有规则数据到内存缓存中。
     */
    private void initializeCaches() {
        routingCache.clear();
        stationCache.clear();
        // 缓存工艺路线
        List<ProdWkpro> allRoutes = prodWkproMapper.selectList(null);
        routingCache.putAll(allRoutes.stream()
                .sorted(Comparator.comparing(ProdWkpro::getSequence))
                .collect(Collectors.groupingBy(ProdWkpro::getPid)));
        // 缓存工位信息
        List<Workstation> allStations = workstationMapper.selectList(null);
        stationCache.putAll(allStations.stream()
                .collect(Collectors.groupingBy(Workstation::getWpid)));
    }

    /**
     * 构建一个 "产品ID:工位ID" -> 标准耗时 的快速查找Map。
     */
    private Map<String, Integer> buildProcessTimeMap() {
        List<ProcessTime> allTimes = processTimeMapper.selectList(null);
        return allTimes.stream()
                .collect(Collectors.toMap(
                        pt -> pt.getPid() + ":" + pt.getWsid(),
                        ProcessTime::getElapsed,
                        (v1, v2) -> v1 // 如果有重复key，保留第一个
                ));
    }
}